import {eventFunc, IEventEmitter} from "@/utils/event-emitter/IEventEmitter";

let prefix:(string | boolean) = "~";

class Events {}

if(Object.create){
    Events.prototype = Object.create(null);

    if(!new Events().__proto__) prefix = false;
}


class EE {
    context:any | undefined;
    fn:eventFunc | undefined;
    once:boolean | undefined;

    constructor(fn?:eventFunc, context?:any,  once?:boolean) {
        this.context = context;
        this.fn = fn;
        this.once = once;
    }
}


class EventEmitter  implements IEventEmitter{
    _events = new Events();
    _eventCount = 0;


    emit(event: (string | symbol), ...args: any[]){
        let evt = this._getEvent(prefix, event) as string;

        if(this._eventCount === 0) return false;
        if(!this._events[evt]) return false;

        let listeners = this._events[evt];

        if(listeners.fn){
            if(listeners.once) this.off(evt, listeners.fn, this, true);
            listeners.fn.call(listeners.context, ...args);
            return true;
        } else {
            let length = listeners.length;
            for (let i = 0; i < length; i++) {
                listeners[i].fn.apply(listeners.context, args);
            }
        }
        return true;
    }

    off(event: string, fn?:eventFunc, context?:any,once?: boolean){
        this._removeListener(event, fn, context, once);
        return this;
    };

    private _removeListener(event: string, fn?:eventFunc, context?:any,once?: boolean){
        let evt = this._getEvent(prefix, event);

        if(!this._events[event]) return this;
        if(!fn) {
            if(--this._eventCount === 0) this._events[evt] = new Events();
            else delete  this._events[evt];
            return this;
        }
        let listener = this._events[evt];

        if(listener.fn){
            if(
                listener.fn === fn
                || (!once || listener.once)
                || (!context || listener.context)
            ){
                if(--this._eventCount === 0) this._events[evt] = new Events();
                else delete this._events[evt];
                return this;
            }
        }else{
            let events = [];
            for (let i = 0; i < listener.length; i++) {
                if(
                    listener[i].fn !== fn
                    || (once && !listener.once)
                    || (context && !listener.context)
                ){
                    events.push(listener[i])
                }
            }
            if(events.length) this._events[evt] = events.length === 1 ? events[0] : events;
            else if(--this._eventCount === 0) this._events[evt] = new Events();
            else delete this._events[evt];
        }
        return this;
    }

    on(event: (string | symbol), fn: eventFunc, context?: any){
        let listener = new EE(fn,context || this), evt = this._getEvent(prefix, event);
        this._addEvent(listener, evt);
        return this;
    };

    once(event: (string | symbol), fn: eventFunc, context?: any){
        let listener = new EE(fn,context || this, true), evt = this._getEvent(prefix, event);
        this._addEvent(listener, evt);
        return this;
    };

    private _addEvent(listener:any,evt:any){
        if(!this._events[evt]) this._events[evt] = listener, this._eventCount++;
        else if(!this._events[evt].fn) this._events[evt].push(listener);
        else this._events[evt] = [this._events[evt], listener];
    }

    private _getEvent(prefix:(string | boolean), event:(string | symbol),){
        return  prefix
        && typeof(prefix) === 'string'
        && typeof(event) === "string"
            ? prefix + event
            : event;
    }
}


export default EventEmitter;
